package analisadorLexico;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import util.ArquivoUtil;

/**
 * Classe Para Validar o fase Léxica da Gramática
 * 
 * @author Alan L Cardoso
 * 
 */
public class AnalisadorLexico {

	private List<Token> tokens = new ArrayList<Token>();

	private List<String> erros = new ArrayList<String>();

	private BufferedReader arquivoGramatica;

	private Integer valorInicialNaoTerminal;

	private Integer valorInicialTerminal;

	/**
	 * Método que recebe o arquivo no Analisador Léxico
	 * 
	 * @param Arquivo
	 *            de entrada GLC valida
	 */
	public AnalisadorLexico(BufferedReader arquivoGramatica) {
		this.arquivoGramatica = arquivoGramatica;
	}

	/**
	 * Método para exibir a lista de tokens
	 */
	public void mostrarToken() {
		for (Token token : this.tokens) {
			System.out.println(token);
		}
	}

	/**
	 * Método que fará a analise léxica
	 * 
	 * @param caminhoArquivoLog
	 * @param arquivoLog
	 * @throws IOException
	 */
	public void analisar(String caminhoArquivoLog, PrintWriter arquivoLog) throws IOException {

		if (!analisar()) {

			ArquivoUtil.escreverArquivo("Foram Detectado Erros Durante a Análise Léxica.\n\n", arquivoLog);
			ArquivoUtil.escreverArquivo("== == == Início da Lista de Erros == == ==\n\n", arquivoLog);

			for (String erro : this.erros) {
				ArquivoUtil.escreverArquivo(erro + "\n", arquivoLog);
			}

			ArquivoUtil.escreverArquivo("\n== == == Fim da Lista de Erros == == ==\n\n", arquivoLog);

			ArquivoUtil.fecharArquivo(arquivoLog);
			System.out.println("Favor analisar o arquivo log.txt ");
			System.exit(0);
		}

	}

	/**
	 * Método do analisador léxico, responsável pela validação do arquivo de
	 * entrada.
	 * 
	 * @param arquivoLog
	 * 
	 * @return Se a analise foi concluída com Sucesso ou não
	 * @throws IOException
	 */

	private boolean analisar() throws IOException {

		String linhaArquivo;
		String imagem;
		Integer countLinha = 0;

		do {
			linhaArquivo = this.arquivoGramatica.readLine();

			if (linhaArquivo != null) {
				this.valorInicialNaoTerminal = 0;
				this.valorInicialTerminal = 0;
				countLinha = countLinha + 1;

				StringTokenizer stringTokenizer = new StringTokenizer(linhaArquivo, " ");

				while (stringTokenizer.hasMoreTokens()) {
					imagem = stringTokenizer.nextToken().trim();

					if (imagem.length() < 1) {
						break;

					} else if (Tabelas.ehDelimitador(imagem)) {
						Token token = new Token(imagem, TipoToken.DELIMINITADOR, countLinha,
								linhaArquivo.indexOf(imagem) + 1);
						this.tokens.add(token);

					} else if (imagem.charAt(0) == '<') {
						String retornoNaoTerminal;
						retornoNaoTerminal = getNaoTerminal(linhaArquivo, countLinha);

						if (retornoNaoTerminal != null) {
							stringTokenizer = new StringTokenizer(retornoNaoTerminal);
						} else {
							break;
						}

					} else if (imagem.charAt(0) == '\'') {
						String retornoTerminalLiteral;
						retornoTerminalLiteral = getTerminalLiteral(linhaArquivo, countLinha);

						if (retornoTerminalLiteral != null) {
							stringTokenizer = new StringTokenizer(retornoTerminalLiteral);
						} else {
							break;
						}
					} else if (imagem.matches("\\p{Alpha}\\p{Alnum}+") || imagem.matches("\\p{Alpha}+")
							|| imagem.matches("\\p{Alpha}") || imagem.equalsIgnoreCase("inteiro_Literal")
							|| imagem.equalsIgnoreCase("|") || imagem.equalsIgnoreCase("real_Literal")
							|| imagem.equalsIgnoreCase("string_Literal")) {
						Token token = new Token(imagem, TipoToken.TERMINAL_CLASSE, countLinha,
								linhaArquivo.indexOf(imagem) + 1);
						this.tokens.add(token);
					} else {
						this.erros.add("Token não Reconhecido " + imagem.toString() + ". Linha/Coluna: " + countLinha
								+ "/" + linhaArquivo.indexOf(imagem));
					}
				}
			}
		} while (linhaArquivo != null);

		Token token = new Token("$", TipoToken.EOF, countLinha, 1);
		this.tokens.add(token);

		if (this.erros.size() > 0) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * @param Linha
	 *            em analise do arquivo
	 * @param Número
	 *            da linha em analise
	 * @return Token Valido
	 */
	private String getTerminalLiteral(String linhaArquivo, Integer countLinha) {

		String novaString;
		Integer valorPosicaoProxCaracter = 0;

		if (this.valorInicialTerminal != 0) {
			valorPosicaoProxCaracter = this.valorInicialTerminal;
		}

		this.valorInicialTerminal = linhaArquivo.indexOf("\'", valorPosicaoProxCaracter + 1);
		valorPosicaoProxCaracter = linhaArquivo.indexOf("\'", this.valorInicialTerminal + 1);

		if (valorPosicaoProxCaracter == -1) {
			this.erros.add("Caracter de encerramento \"'\" não Encontrado. Linha: " + countLinha);
		} else {
			novaString = linhaArquivo.substring(this.valorInicialTerminal + 1, valorPosicaoProxCaracter);

			Token token = new Token(novaString.replace("\'", ""), TipoToken.TERMINAL_LITERAL, countLinha,
					linhaArquivo.indexOf(novaString, this.valorInicialTerminal));
			this.tokens.add(token);

			linhaArquivo = linhaArquivo.substring(valorPosicaoProxCaracter + 1, linhaArquivo.length());

			this.valorInicialTerminal = valorPosicaoProxCaracter + 1;

			return linhaArquivo;
		}

		return null;
	}

	/**
	 * @param Linha
	 *            em analise do arquivo
	 * @param Número
	 *            da linha em analise
	 * @return Token Valido
	 */
	private String getNaoTerminal(String linhaArquivo, Integer countLinha) {

		String novaString;
		Integer valorPosicaoProxCaracter = 0;

		if (this.valorInicialNaoTerminal != 0) {
			valorPosicaoProxCaracter = this.valorInicialNaoTerminal + 1;
		} else {
			valorPosicaoProxCaracter = this.valorInicialNaoTerminal;
		}

		this.valorInicialNaoTerminal = linhaArquivo.indexOf("<", valorPosicaoProxCaracter);
		valorPosicaoProxCaracter = linhaArquivo.indexOf(">", this.valorInicialNaoTerminal + 1);

		if (valorPosicaoProxCaracter == -1) {
			this.erros.add("Caracter de encerramento \">\" não Encontrado. Linha: " + countLinha);
		} else {
			novaString = linhaArquivo.substring(this.valorInicialNaoTerminal + 1, valorPosicaoProxCaracter);

			Token token = new Token(novaString.replace("<", "").replace(">", ""), TipoToken.NAO_TERMINAL, countLinha,
					linhaArquivo.indexOf(novaString, this.valorInicialNaoTerminal));
			this.tokens.add(token);

			linhaArquivo = linhaArquivo.substring(valorPosicaoProxCaracter + 1, linhaArquivo.length());

			this.valorInicialNaoTerminal = valorPosicaoProxCaracter + 1;

			return linhaArquivo;
		}
		return null;
	}

	/**
	 * Gets and Sets
	 */
	public List<Token> getTokens() {
		return tokens;
	}

	public void setTokens(List<Token> tokens) {
		this.tokens = tokens;
	}
}
